using System.Collections.Generic;
using System.Xml;
using System;
using System.Xml.Schema;
using System.Text;
using System.IO;
using System.Globalization;
using System.Threading;
using WMM.Tapioca.Instance;
using WMM.Tapioca.Taxonomy;
using WMM.Tapioca.Linkbase;
using WMM.Tapioca.Utilities;
using System.Xml.Linq;
using System.Linq;

namespace WMM.Tapioca.Instance
{
    /// <summary>
    /// XBRL instances are XML fragments with root element, xbrl. XBRL instances contain facts, 
    /// with each fact corresponding to a concept defined in their supporting DTS. 
    /// XBRL instances also contain context and unit elements that provide additional i
    /// nformation needed to interpret the facts in the instance.
    /// </summary>
    public class InstanceDocument : XbrlDocument
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private XElement xbrlRootElement;

        /// <summary>
        /// The root XML node for the XBRL fragment.
        /// </summary>
        /// <value>
        /// The XBRL root element.
        /// </value>
        public XElement XbrlRootElement
        {
            get
            {
                return xbrlRootElement;
            }
        }

        private string path;

        /// <summary>
        /// The path to the XML document used to contain the XBRL data.
        /// </summary>
        public string Path
        {
            get
            {
                return path;
            }
        }

        private List<Context> contexts;
        private List<Fact> facts;
        private List<Unit> units;
        private List<ExtendedLink> footnoteLinks;

        /// <summary>
        /// A collection of <see cref="Context"/> objects representing all contexts found in the fragment.
        /// </summary>
        public List<Context> Contexts
        {
            get
            {
                return contexts;
            }
        }

        /// <summary>
        /// A collection of <see cref="Fact"/> objects representing all facts found in the fragment.
        /// </summary>
        public List<Fact> Facts
        {
            get
            {
                return facts;
            }
        }

        /// <summary>
        /// A collection of <see cref="Unit"/> objects representing all units found in the fragment.
        /// </summary>
        public List<Unit> Units
        {
            get
            {
                return units;
            }
        }

        /// <summary>
        /// A collection of <see cref="ExtendedLink"/> objects representing all footnote links
        /// found in the fragment.
        /// </summary>
        public List<ExtendedLink> FootnoteLinks
        {
            get
            {
                return footnoteLinks;
            }
        }


        /// <summary>
        /// The constructor for the InstanceDocument class.
        /// </summary>
        public InstanceDocument(XbrlDocument xbrlDocument, XmlUrlResolver resolver)
        {
            Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

            path = xbrlDocument.PathFile;

            log.Info("Creating instance: " + xbrlDocument.FileName);

            this.FileName = xbrlDocument.FileName;
            this.PathFile = xbrlDocument.PathFile;
            this.ThisXDocument = xbrlDocument.ThisXDocument;
            this.targetNamespace = xbrlDocument.ThisXDocument.Root.Name.NamespaceName;
            this.namespaceManager = xbrlDocument.ThisXDocument.CreateNamespaceManager();
            this.xbrlRootElement = xbrlDocument.ThisXDocument.Root;
            this.DocType = this.ThisXDocument.Root.Name.LocalName;

            Dts = new DTS(this, resolver);
        }


        /// <summary>
        /// The constructor for the InstanceDocument class.
        /// </summary>
        public InstanceDocument(string instanceFilename, XmlUrlResolver resolver)
            : base(null, instanceFilename, "", "", resolver)
        {
            Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

            path = System.IO.Path.GetDirectoryName(instanceFilename);

            log.Info("Creating instance: " + instanceFilename);

            xbrlRootElement = ThisXDocument.Root;
            Dts = new DTS(this, resolver);
        }



        #region Public Methods

        /// <summary>
        /// Returns a reference to the context having the supplied context ID.
        /// </summary>
        /// <param name="contextId">
        /// The ID of the context to return.
        /// </param>
        /// <returns>
        /// A reference to the context having the supplied context ID.
        /// A null is returned if no contexts with the supplied context ID is available.
        /// </returns>
        public Context GetContext(string contextId)
        {
            foreach (Context currentContext in contexts)
            {
                if (currentContext.Id == contextId)
                {
                    return currentContext;
                }
            }

            return null;
        }

        /// <summary>
        /// Returns a reference to the unit having the supplied unit ID.
        /// </summary>
        /// <param name="unitId">
        /// The ID of the unit to return.
        /// </param>
        /// <returns>
        /// A reference to the unit having the supplied unit ID.
        /// A null is returned if no units with the supplied unit ID is available.
        /// </returns>
        public Unit GetUnit(string unitId)
        {
            foreach (Unit currentUnit in units)
            {
                if (currentUnit.Id == unitId)
                {
                    return currentUnit;
                }
            }

            return null;
        }


        /// <summary>
        /// Gets the schema associated with a given namespace prefix.
        /// </summary>
        /// <param name="namespaceTarget">
        /// The namespace prefix whose schema should be returned.
        /// </param>
        /// <returns>
        /// A reference to the XBRL schema containing the specified namespace prefix. A null
        /// is returned if the given namespace prefix is not found in any of the XBRL schemas.
        /// </returns>
        public TaxonomySchema GetXbrlSchemaForNamespace(string namespaceTarget)
        {
            if (namespaceTarget.Length == 0)
            {
                return null;
            }

            foreach (TaxonomySchema currentSchema in Dts.TaxonomySchemas)
            {
                if (currentSchema.TargetNamespace.Equals(namespaceTarget))
                {
                    return currentSchema;
                }
            }

            return null;
        }

        //===============================================================================
        #endregion
        //===============================================================================


        //===============================================================================
        #region Private Methods
        //===============================================================================


        internal void ReadContexts()
        {
            contexts = new List<Context>();

            IEnumerable<XElement> contextElements = from c in ThisXDocument.Root.Elements() 
                                                 where c.Name.LocalName.Equals("context")
                                                 select c;

            foreach (XElement currentContext in contextElements)
            {
                contexts.Add(new Context(this, currentContext, Dts));
            }
        }

        /// <summary>
        /// Reads all of the facts in the XBRL fragment and creates an object for each.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Concept instances can be any of the following:
        /// </para>
        /// <para>
        /// <list type="bullet">
        /// <item>
        /// an item, represented in an XBRL schema by an element with a substitution group of "item"
        /// and represented by an <see cref="FactItem"/> object
        /// </item>
        /// <item>
        /// a tuple, represented in an XBRL schema by an element with a substitution group of "tuple"
        /// and represented by an <see cref="FactTuple"/> object
        /// </item>
        /// </list>
        /// </para>
        /// </remarks>
        internal void ReadFacts()
        {
            facts = new List<Fact>();

            foreach (XElement currentElement in ThisXDocument.Root.Elements())
            {
                Fact currentFact = Fact.Create(this, currentElement, Dts);

                if (currentFact != null)
                {
                    facts.Add(currentFact);
                }
            }
        }


        internal void ReadUnits()
        {
            units = new List<Unit>();

            IEnumerable<XElement> unitElements = from c in ThisXDocument.Root.Elements()
                                              where c.Name.LocalName.Equals("unit")
                                              select c;

            foreach (XElement currentUnit in unitElements)
            {
                units.Add(new Unit(currentUnit, Dts));
            }
        }


        internal void ReadFootnoteLinks()
        {
            footnoteLinks = new List<ExtendedLink>();

            IEnumerable<XElement> footnoteLinkElements = from c in ThisXDocument.Root.Elements()
                                                         where c.Name.LocalName.Equals("footnoteLink")
                                                         select c;

            foreach (XElement currentFootNoteLink in footnoteLinkElements)
            {
                footnoteLinks.Add(new ExtendedLink(currentFootNoteLink, this.XbrlRootElement, this));
            }
        }


        //===============================================================================
        #endregion
        //===============================================================================


    }
}
